use crate::api::model::{Check, ContentType, HttpMethod, RequestSpec};
use crate::api::schema::FieldOrder;
use serde::Deserialize;
use snafu::{ResultExt, whatever};
use std::collections::HashMap;
use std::path::Path;
use std::time::Duration;

#[derive(Debug)]
pub struct ApiRegistry {
    pub templates: HashMap<String, Template>,
}

#[derive(Debug)]
pub struct Template {
    // which url will override by flow
    pub spec: RequestSpec,
    pub fields: FieldOrder,
}

#[derive(Debug, Deserialize)]
struct ApiFile {
    interfaces: HashMap<String, InterfaceFile>,
}

#[derive(Debug, Deserialize)]
struct InterfaceFile {
    endpoint: String,
    method: String,
    content_type: String,
    #[serde(default)]
    headers: HashMap<String, String>,
    #[serde(default)]
    timeout_secs: Option<u64>,
    #[serde(default)]
    checks: Vec<CheckFile>,
    #[serde(default)]
    fields: Vec<String>,
    #[serde(default)]
    omit_null: bool,
}

#[derive(Debug, Deserialize)]
struct CheckFile {
    r#type: String,
    field: Option<String>,
    success: Option<serde_json::Value>,
}

impl ApiRegistry {
    pub async fn load(path: &Path, base_url: String) -> crate::Result<ApiRegistry> {
        let raw = tokio::fs::read_to_string(&path)
            .await
            .whatever_context("read api file failed")?;
        let file = toml::from_str::<ApiFile>(&raw).whatever_context("parse api file failed")?;

        let mut templates = HashMap::new();

        for (name, itf) in file.interfaces {
            let method = match itf.method.as_str() {
                "GET" => HttpMethod::GET,
                "POST" => HttpMethod::POST,
                _ => unimplemented!(),
            };

            let content_type = match itf.content_type.as_str() {
                "json" => ContentType::Json,
                "form" => ContentType::Form,
                _ => whatever!("unsupported content type {}", itf.content_type),
            };

            let mut checks = vec![];

            for check in itf.checks {
                match check.r#type.as_str() {
                    "http2xx" => checks.push(Check::Http2xx),
                    "success" => {
                        let field = match &check.field {
                            None => whatever!("ok check field not set"),
                            Some(field) => field.clone(),
                        };

                        let success = match &check.success {
                            None => whatever!("ok check success not set"),
                            Some(success) => match success.as_bool() {
                                None => whatever!("ok check success not set"),
                                Some(success) => success,
                            },
                        };

                        checks.push(Check::Ok { field, success })
                    }
                    "code" => {
                        let field = match &check.field {
                            None => whatever!("code check field not set"),
                            Some(field) => field.clone(),
                        };

                        let success = match &check.success {
                            None => whatever!("code check success not set"),
                            Some(success) => match success.as_i64() {
                                None => whatever!("code check success not set"),
                                Some(success) => success,
                            },
                        };

                        checks.push(Check::Code { field, success })
                    }
                    _ => {}
                }
            }

            let spec = RequestSpec {
                base_url: base_url.clone(),
                endpoint: itf.endpoint,
                method,
                content_type,
                headers: itf.headers.into_iter().collect(),
                timeout: itf.timeout_secs.map(Duration::from_secs),
                checks,
            };

            let fields = FieldOrder {
                names: itf.fields,
                omit_nulls: itf.omit_null,
            };

            templates.insert(name.to_string(), Template { spec, fields });
        }

        Ok(ApiRegistry { templates })
    }
}
